/** filestream.d - File stream class

Authors:
	Wei Li (oldrev@gmail.com)

Copyright:
	Copyright (C) 2007 The Dotmars Team.

License:
	BSD

*/

module dotmars.io.filestream;

import dotmars.base.string;
import dotmars.io.stream;
import dotmars.platform.base;
import dotmars.text.utf;

public import dotmars.io.ioexcept;

// Windows API:
version(Win32) {
extern(Windows) {

	BOOL SetFilePointerEx(
			HANDLE hFile,
			LARGE_INTEGER liDistanceToMove,
			PLARGE_INTEGER lpNewFilePointer,
			DWORD dwMoveMethod
			);
	BOOL GetFileSizeEx(
			HANDLE hFile,
			PLARGE_INTEGER lpFileSize
			);
} } //version(Win32)


public enum FileMode : ubyte
{
	Open,
	Create,
	CreateNew,
	OpenOrCreate,
	Truncate,
	Append,
}


public enum FileAccess : ubyte
{
	Read,
	Write,
	ReadWrite
}


public enum FileShare : ubyte
{
	None,
	Read,
	Write,
	ReadWrite
}


class FileStream : Stream
{
    version(Win32) {

	public alias HANDLE Handle;
	public const Handle InvalidHandle = INVALID_HANDLE_VALUE;

}//version(Win32)

version(Posix) {
	public alias int Handle;
	public const Handle InvalidHandle = -1;
} //version(Posix)

	protected FileAccess	m_access;
	protected FileMode		m_mode;
    protected FileShare     m_share;
    protected Handle  		m_handle;

	debug
	{
		protected void assertValid()
		{
			assert(m_handle != InvalidHandle);
		}
	}

	public alias Stream.read read;
	public alias Stream.write write;

	public this(char[] path, FileMode m = FileMode.OpenOrCreate, 
			FileAccess a = FileAccess.ReadWrite, 
            FileShare s = FileShare.Read)
	{
		open(path, m, a, s);
	}

	public ~this() {
		close();
	}

	Handle handle()
	{
		return m_handle;
	}

	public override bool seekable() { return true; }

	public override bool readable() 
	{ 
		return m_access != FileAccess.Write ? 
			true : false;
	}

	public override bool writable()
	{
		return m_access != FileAccess.Read ?
			true : false;
	}

	/**
	  * End of stream
	  */
	public override bool eos()
	in
	{
		debug assertValid;
	}
	body
	{
		return position >= length;
	}


version(Win32) {

	protected void open(char[] path, FileMode mode, FileAccess access, FileShare share)
	in
	{
		assert(!(mode == FileMode.Append && readable));
		assert(!(mode == FileMode.Truncate && readable));
	}
	body
	{
		m_access = access;
		m_mode = mode;
        m_share = share;

		DWORD FileAccessMap(FileAccess acc)
		{	
			switch(acc)
			{
				case FileAccess.Read:
					return GENERIC_READ;

				case FileAccess.Write:
					return GENERIC_WRITE;

				case FileAccess.ReadWrite:
					return GENERIC_READ | GENERIC_WRITE;

				default:
					assert(false);
					break;
			}
		}

		DWORD FileModeMap(FileMode mod)
		{
			switch(mod)
			{
				case FileMode.Open:
					return OPEN_EXISTING;

				case FileMode.Create:
					return CREATE_ALWAYS;

				case FileMode.CreateNew:
					return CREATE_NEW;

				case FileMode.OpenOrCreate:
					return OPEN_ALWAYS;

				case FileMode.Truncate:
					return TRUNCATE_EXISTING;

				case FileMode.Append:
					return OPEN_ALWAYS;

				default:
					assert(false);
					break;
			}
		}

		DWORD FileShareMap(FileShare shr)
		{
			switch(shr)
			{
				case FileShare.Read:
					return FILE_SHARE_READ;

				case FileShare.Write:
					return FILE_SHARE_WRITE;

				case FileShare.ReadWrite:
					return FILE_SHARE_READ | FILE_SHARE_WRITE;

				case FileShare.None:
					return 0;

				default:
					assert(false);
					break;
			}
		}

		Handle h = CreateFileW(toUtf16z(path), FileAccessMap(access), FileShareMap(share), null, 
				FileModeMap(mode), FILE_FLAG_RANDOM_ACCESS, null);

		if(h == INVALID_HANDLE_VALUE)
			throw new IOException("Failed to open or create " ~ path);

		m_handle = h;
		if(m_mode == FileMode.Append)
			seek(0, SeekOrigin.End);
	}

	public override void close()
	{
		if(m_handle != InvalidHandle)
		{
			CloseHandle(m_handle);
			m_handle = InvalidHandle;
		}
	}

	public override void flush()
	in
	{
		debug assertValid();
	}
	body
	{
		FlushFileBuffers(m_handle);
	}


	public override long seek(long pos, SeekOrigin origin)
	in
	{
		debug assertValid();
		assert(seekable);
		assert(pos < length);
	}
	body
	{
		LARGE_INTEGER lipos, newpos;
		lipos.QuadPart = pos;
		BOOL r = SetFilePointerEx(m_handle, 
				lipos, &newpos, origin);
		if(r == FALSE) throw new IOException("Failed to seek");
		return newpos.QuadPart;
	}

	public override long length()
	in
	{
		debug assertValid();
	}
	body
	{
		LARGE_INTEGER lisize;
		BOOL r = GetFileSizeEx(m_handle, &lisize);
		if(r == FALSE)throw new IOException("Failed to get file length");
		return lisize.QuadPart;
	}

	public override void length(long len)
	in
	{
		debug assertValid();
		assert(seekable && writable);
		assert(len >= 0);
	}
	body
	{
		position = len;
		BOOL r = SetEndOfFile(m_handle);
		if(r == TRUE)
			throw new IOException("Failed to set file length");
	}

	public override void write(void* data, size_t count)
	in
	{
		debug assertValid();
		assert(writable);
		assert(data !is null);
		assert(count > 0);
	}
	body
	{
		DWORD written = 0;
		DWORD dwcount = count;
		BOOL r = WriteFile(m_handle, data, dwcount, &written, null);
		if(r == FALSE || written != count) 
			throw new IOException("Failed to write file");
	}

	public override size_t read(void* data, size_t count)
	in
	{
		debug assertValid();
		assert(readable);
		assert(data !is null);
		assert(count > 0);
	}
	body
	{
		DWORD read = 0;
		DWORD dwcount = count;
		BOOL r = ReadFile(m_handle, data, dwcount, &read, null);
		if(r == FALSE) throw new IOException("Failed to read file");
		return read;
	}
} // version(Win32)


version(Posix) {

	protected void open(char[] path, FileMode mode, FileAccess access, FileShare share)
	in
	{
		assert(!(mode == FileMode.Append && readable));
		assert(!(mode == FileMode.Truncate && readable));
	}
	body
	{
		m_access = access;
		m_mode = mode;
        m_share = share;

		int fileModeMap(FileMode fm) 
		{
			switch(fm)
			{
				case FileMode.Open:
					return 0;
					break;

				case FileMode.CreateNew:
					return O_CREAT | O_EXCL;
					break;

				case FileMode.Create:
					return O_CREAT | O_TRUNC;
					break;

				case FileMode.Truncate:
					return O_TRUNC;
					break;

				case FileMode.Append:
					return O_APPEND;
					break;

				default:
					assert(false);
					break;
			}
		}

		int fileAccessMap(FileAccess fa) 
		{
			switch(fa)
			{
				case FileAccess.Read:
					return O_RDONLY;
					break;

				case FileAccess.Write:
					return O_WRONLY;
					break;

				case FileAccess.ReadWrite:
					return O_RDWR;
					break;

				default:
					assert(false);
					break;
			}

		}

		int fileShareMap(FileShare fs) 
		{
			switch(fs)
			{
				case FileShare.None:
					return 0;
					break;

				case FileShare.Read:
					return 0640;
					break;

				case FileShare.Write:
					return 0620;
					break;

				case FileShare.ReadWrite:
					return 0660;
					break;

				defalt:
					assert(false);
					break;
			}
		}

		// Posix's open() function
		m_handle = dotmars.platform.posix.fcntl.open(
				toStringz(path), fileModeMap(mode) | fileAccessMap(access), 
				fileShareMap(share));

		if(m_handle == -1)
			throw new IOException("Failed to open or create " ~ path);
	}

	public override void close()
	{
		if(m_handle != InvalidHandle)
		{
			dotmars.platform.posix.unistd.close(m_handle);
			m_handle = InvalidHandle;
		}
	}

	public override void flush()
	in
	{
		debug assertValid();
	}
	body
	{
		//Posix 系统里似乎没有 flush 函数
	}


	public override long seek(long pos, SeekOrigin origin)
	in
	{
		debug assertValid();
		assert(pos < length);
	}
	body
	{
		return lseek64(m_handle, pos, origin);
	}

	public override long length()
	in
	{
		debug assertValid();
		assert(seekable);
	}
	out(r)
	{
		assert(r >= 0);
	}
	body
	{
		long curpos = position;
		long ret = seek(0, SeekOrigin.End);
		position = curpos;
		return ret;
	}

	public override void length(long len)
	in
	{
		debug assertValid();
		assert(seekable && writable);
		assert(len >= 0);
	}
	body
	{
		int r = ftruncate(m_handle, len);
		if(r == -1)
			throw new IOException("Failed to set file length");
	}

	public override void write(void* data, size_t count)
	in
	{
		debug assertValid();
		assert(writable);
		assert(data !is null);
		assert(count > 0);
	}
	body
	{
		int written = 
			dotmars.platform.posix.unistd.write(m_handle, data, count);

		//avoid to overflow
		if(written == -1 || written != count)
			throw new IOException("Failed to write"); 
	}

	public override size_t read(void* data, size_t count)
	in
	{
		debug assertValid();
		assert(readable);
		assert(data !is null);
		assert(count > 0);
	}
	body
	{
		int r = 
			dotmars.platform.posix.unistd.read(m_handle, data, count);
		if(r == -1)
			throw new IOException("Failed to read");
		return r;
	}
} // version(Posix)


} //class FileStream



